/* Station THAQ NODEMCU ESP8266 version 5.1 - octobre 2025
  
*/

#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>
#include <ArduinoJson.h>
#include "DHT.h"
#include <ESP8266HTTPClient.h>
#include <TM1637Display.h>
#include <time.h>
#include <EEPROM.h>

// ---- WiFi ----
const char* ssid = "TON_WIFI";
const char* password = "TON_MOT_DE_PASSE";

// ---- Telegram ----
#define BOT_TOKEN "TON_BOT_TOKEN"
#define CHAT_ID "TON_CHAT_ID"
WiFiClientSecure secured_client;
UniversalTelegramBot bot(BOT_TOKEN, secured_client);

// ---- DHT11 ----
#define DHTPIN 2
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);

// ---- LED interne ----
#define LED_WIFI LED_BUILTIN
unsigned long lastBlink = 0;
bool ledState = LOW;

// ---- MQ135 (Qualité d'air) ----
#define MQ135_PIN A0

// ---- Cooldown alertes (6 heures) ----
const unsigned long cooldown = 21600000;
unsigned long lastTempHighAlert = 0;
unsigned long lastTempLowAlert = 0;
unsigned long lastHumHighAlert = 0;
unsigned long lastHumLowAlert = 0;
unsigned long lastAirAlert = 0;
unsigned long lastDewPointAlert = 0;
unsigned long lastCheck = 0;

// ---- OpenWeatherMap ----
const char* WEATHER_API_KEY = "TON_API_KEY";
float tunisTemp = 0;
float tunisFeels = 0;
float tunisHum = 0;
float tunisWind = 0;
bool tunisRain = false;
String tunisDescription = "";

// ---- Afficheur TM1637 ----
#define CLK 12
#define DIO 14
TM1637Display display(CLK, DIO);

// ---- Mode d’affichage choisi via Telegram ----
enum ModeAffichage {MODE_TEMP, MODE_HUM, MODE_AIR, MODE_TIME};
ModeAffichage currentMode = MODE_TEMP;

// ---- Luminosité automatique simplifiée ----
bool autoLuminosite = true;
uint8_t luminositeFixe = 0x07;
uint8_t luminositeActuelle = 0x07;
uint8_t calculLuminositeSimplifie(int hour){
  const uint8_t minBright = 0x02;
  const uint8_t maxBright = 0x07;
  if(hour >= 6 && hour < 19) return maxBright;
  else return minBright;
}

// ---- EEPROM ----
#define EEPROM_SIZE 64
void saveConfig() {
  bool modif = false;
  uint8_t cm = (uint8_t)currentMode;
  if (EEPROM.read(0) != cm) { EEPROM.write(0, cm); modif = true; }
  uint8_t al = autoLuminosite ? 1 : 0;
  if (EEPROM.read(1) != al) { EEPROM.write(1, al); modif = true; }
  if (EEPROM.read(2) != luminositeFixe) { EEPROM.write(2, luminositeFixe); modif = true; }
  if (modif) EEPROM.commit();
}
void loadConfig() {
  uint8_t b0 = EEPROM.read(0);
  if (b0 <= (uint8_t)MODE_TIME) currentMode = (ModeAffichage)b0;
  uint8_t b1 = EEPROM.read(1);
  autoLuminosite = (b1 == 1);
  uint8_t b2 = EEPROM.read(2);
  if (b2 >= 0x00 && b2 <= 0x0F) luminositeFixe = b2;
  luminositeActuelle = autoLuminosite ? 0x07 : luminositeFixe;
}

// ---- Prototypes ----
void sendConfigTelegram();
String getCurrentDate();
String getCurrentDateTime();

// ---- Redémarrage limité à 1 fois/24h ----
char lastRebootDate[11] = ""; 
void initRebootTracking() {
  for(int i=0;i<10;i++){
    lastRebootDate[i] = EEPROM.read(10+i);
  }
  lastRebootDate[10] = '\0';
}
void saveRebootDate() {
  for(int i=0;i<10;i++){
    EEPROM.write(10+i, lastRebootDate[i]);
  }
  EEPROM.commit();
}
bool canReboot() {
  String today = getCurrentDate();
  return (String(lastRebootDate) != today);
}
void markRebootDone() {
  String today = getCurrentDate();
  today.toCharArray(lastRebootDate, 11);
  saveRebootDate();
}

// ---- Températures locales min/max ----
float tempMin = 1000;
float tempMax = -1000;
int heureTempMin = 0;
int minuteTempMin = 0;
int heureTempMax = 0;
int minuteTempMax = 0;
String dateTemp = "";
String dateTempMin = "";
String dateTempMax = "";

// ---- Variables Wi-Fi ----
bool wifiFailing = false;
unsigned long lastWiFiAttempt = 0;
const unsigned long WIFI_RETRY_INTERVAL = 10000;
unsigned int wifiLossCount = 0;
String wifiLossDate = "";

// ---- Uptime ----
unsigned long startMillis = 0;
String startDate = "";
time_t startEpoch = 0; // <-- ajout pour le calcul précis de la durée

// ---- Fonctions utilitaires ----
float calculPointRosee(float T, float RH){
  float a = 17.27;
  float b = 237.7;
  float gamma = (a * T) / (b + T) + log(RH / 100.0);
  return (b * gamma) / (a - gamma);
}
String qualiteAirInterpret(int value){
  if(value < 400) return "Bonne";
  if(value < 800) return "Moyenne";
  if(value < 1200) return "Mauvaise";
  return "Dangereuse";
}
String etatVent(float v){
  if(v < 2) return "Calme – fumée verticale";
  else if(v < 6) return "Très légère brise – feuilles immobiles";
  else if(v < 12) return "Légère brise – feuilles en mouvement";
  else if(v < 20) return "Petite brise – drapeaux flottent";
  else if(v < 29) return "Jolie brise – poussières soulevées";
  else if(v < 39) return "Bonne brise – branches en mouvement";
  else if(v < 50) return "Vent frais – grandes branches agitées";
  else if(v < 62) return "Grand vent frais – arbres entiers bougent";
  else if(v < 75) return "Coup de vent – branches cassées possibles";
  else if(v < 89) return "Fort coup de vent – dommages mineurs";
  else if(v < 103) return "Tempête – arbres déracinés";
  else if(v < 118) return "Violente tempête – dommages sérieux";
  else return "Ouragan – destruction importante";
}

// ---- Météo ----
void getWeatherTunis() {
  if (WiFi.status() == WL_CONNECTED) {
    HTTPClient http;
    WiFiClient client;
    String url = "http://api.openweathermap.org/data/2.5/weather?lat=36.808&lon=10.097&appid=" + String(WEATHER_API_KEY) + "&units=metric&lang=fr";
    http.begin(client, url);
    int httpCode = http.GET();
    if (httpCode == 200) {
      String payload = http.getString();
      DynamicJsonDocument doc(2048);
      deserializeJson(doc, payload);
      tunisTemp = doc["main"]["temp"].as<float>();
      tunisFeels = doc["main"]["feels_like"].as<float>();
      tunisHum = doc["main"]["humidity"].as<float>();
      tunisWind = doc["wind"]["speed"].as<float>() * 3.6;
      tunisRain = doc.containsKey("rain");
      tunisDescription = String((const char*)doc["weather"][0]["description"]);
    }
    http.end();
  }
}

// ---- Date ----
String getCurrentDate(){
  time_t nowTime = time(nullptr);
  struct tm * timeinfo = localtime(&nowTime);
  char currentDate[11];
  sprintf(currentDate,"%04d-%02d-%02d",timeinfo->tm_year+1900,timeinfo->tm_mon+1,timeinfo->tm_mday);
  return String(currentDate);
}
String getCurrentDateTime(){
  time_t nowTime = time(nullptr);
  struct tm * timeinfo = localtime(&nowTime);
  char buf[20];
  sprintf(buf, "%04d-%02d-%02d %02d:%02d", timeinfo->tm_year+1900, timeinfo->tm_mon+1, timeinfo->tm_mday, timeinfo->tm_hour, timeinfo->tm_min);
  return String(buf);
}

// ---- Rapport météo ----
void sendReport() {
  float h = dht.readHumidity();
  float t = dht.readTemperature();
  float tempRessentie = dht.computeHeatIndex(t,h,false);
  int airQuality = analogRead(MQ135_PIN);
  float pointRosee = calculPointRosee(t, h);
  String qualiteAir = qualiteAirInterpret(airQuality);

  getWeatherTunis();

  String currentDate = wifiLossDate;
  if(currentDate != getCurrentDate()){
    wifiLossCount = 0;
    wifiLossDate = getCurrentDate();
  }

  String message = "📊 *Rapport Station THAQ 5.1*\n";
  message += "🌡️ Température: " + String(t,0) + " °C\n";
  message += "💧 Humidité: " + String(h,1) + " %\n";
  message += "❄️ Point de rosée: " + String(pointRosee,1) + " °C\n";
  message += "🌫️ Qualité d'air: " + String(airQuality) + " (" + qualiteAir + ")\n";

  message += "📉 Temp. minimale: " + String(tempMin,1) + " °C à ";
  if (dateTempMin.length() > 0) message += dateTempMin + "\n";
  else message += String(heureTempMin) + "h" + (minuteTempMin < 10 ? "0" : "") + String(minuteTempMin) + "\n";

  message += "📈 Temp. maximale: " + String(tempMax,1) + " °C à ";
  if (dateTempMax.length() > 0) message += dateTempMax + "\n\n";
  else message += String(heureTempMax) + "h" + (minuteTempMax < 10 ? "0" : "") + String(minuteTempMax) + "\n\n";

  message += "🌍 *Météo Manouba*\n";
  message += "🌡️ Temp: " + String(tunisTemp,1) + " °C\n";
  message += "💧 Humidité: " + String(tunisHum,1) + " %\n";
  message += "💨 Vent: " + String(tunisWind,1) + " km/h (" + etatVent(tunisWind) + ")\n";
  message += "🌤️ Conditions: " + tunisDescription + "\n";
  message += tunisRain ? "🌧️ Pluie: Oui\n" : "🌧️ Pluie: Non\n";

  bot.sendMessage(CHAT_ID, message, "Markdown");
}

// ---- Rapport technique (mise à jour avec minutes) ----
void sendTechReport() {
  time_t now = time(nullptr);
  unsigned long uptimeSeconds;
  if (startEpoch != 0 && now > startEpoch) {
    uptimeSeconds = (unsigned long)(now - startEpoch);
  } else {
    unsigned long uptimeMillis = millis() - startMillis;
    uptimeSeconds = uptimeMillis / 1000UL;
  }

  unsigned long days = uptimeSeconds / 86400UL;
  unsigned long hours = (uptimeSeconds % 86400UL) / 3600UL;
  unsigned long minutes = (uptimeSeconds % 3600UL) / 60UL;

  String msg = "🧠 *Rapport Technique Station THAQ 5.1*\n\n";
  msg += "🕓 En service depuis: " + startDate + "\n";
  msg += "⏱️ Fonctionnement continu: " + String(days) + " j " + String(hours) + " h " + String(minutes) + " min\n\n";
  msg += "📶 Aujourd'hui reconnexion Wi-Fi: " + String(wifiLossCount) + "\n";
  msg += "🔁 Redémarrage autorisé aujourd'hui: ";
  msg += canReboot() ? "Oui ✅" : "Non ❌";
  
  bot.sendMessage(CHAT_ID, msg, "Markdown");
}

// ---- Connexion Wi-Fi ----
void connectWiFiNonBloquant(){
  if(WiFi.status()==WL_CONNECTED){
    wifiFailing = false;
    return;
  }
  unsigned long now = millis();
  if(now - lastWiFiAttempt < WIFI_RETRY_INTERVAL) return;
  lastWiFiAttempt = now;

  if(!wifiFailing){
    wifiFailing = true;
    wifiLossCount++;
    wifiLossDate = getCurrentDate();
    Serial.println("WiFi perdu !");
  }

  WiFi.begin(ssid,password);
  Serial.println("Tentative reconnexion Wi-Fi...");
}

// ---- Setup ----
void setup() {
  Serial.begin(115200);
  dht.begin();
  EEPROM.begin(EEPROM_SIZE);
  loadConfig();
  initRebootTracking();
  display.setBrightness(luminositeActuelle);
  display.clear();
  pinMode(LED_WIFI,OUTPUT); 
  digitalWrite(LED_WIFI,HIGH);

  WiFi.begin(ssid,password);
  Serial.print("Connexion WiFi...");
  uint8_t dash = 0x40;
  while(WiFi.status()!=WL_CONNECTED){
    for(int i=0;i<4;i++){
      uint8_t seg[4]={0,0,0,0};
      for(int j=0;j<=i;j++) seg[j]=dash;
      display.setSegments(seg);
      delay(400);
    }
    display.clear();
    delay(400);
  }
  uint8_t THAO[4] = {0x78,0x76,0x77,0x3F};
  display.setSegments(THAO);
  delay(5000);
  display.clear();
  Serial.println("\nWiFi connecte !");
  secured_client.setInsecure();
  configTime(3600, 0, "pool.ntp.org", "time.nist.gov");
  time_t now = time(nullptr);
  while(now < 100000){ delay(500); now = time(nullptr); }
  Serial.println("Heure synchronisée !");

  startMillis = millis();
  startDate = getCurrentDateTime();
  startEpoch = time(nullptr); // <-- enregistrement de l'époque de démarrage

  bot.sendMessage(CHAT_ID, "🚀 Station THAQ version 5.1 (octobre 2025) opérationnelle", "Markdown");
}

// ---- Configuration ----
void sendConfigTelegram() {
  String msg = "⚙️ *Configuration actuelle*\n";
  msg += "Mode affichage: ";
  if (currentMode == MODE_TEMP) msg += "Température\n";
  else if (currentMode == MODE_HUM) msg += "Humidité\n";
  else if (currentMode == MODE_AIR) msg += "Qualité d'air\n";
  else if (currentMode == MODE_TIME) msg += "Heure\n";

  msg += "Luminosité: ";
  if (autoLuminosite) msg += "Automatique\n";
  else msg += "Fixe (" + String(luminositeFixe, DEC) + ")\n";

  bot.sendMessage(CHAT_ID, msg, "Markdown");
}

// ---- Loop ----
void loop() {
  connectWiFiNonBloquant();

  if(WiFi.status()==WL_CONNECTED){
    if(millis()-lastBlink>=500){
      lastBlink=millis();
      ledState=!ledState;
      digitalWrite(LED_WIFI,ledState?LOW:HIGH);
    }
  }

  if(millis()-lastCheck>2000){
    lastCheck=millis();
    float h=dht.readHumidity();
    float t=dht.readTemperature();
    int airQuality=analogRead(MQ135_PIN);

    time_t nowTime = time(nullptr);
    struct tm * timeinfo = localtime(&nowTime);

    String currentDate = getCurrentDate();
    if(dateTemp != currentDate){
        tempMin = 1000;
        tempMax = -1000;
        heureTempMin = minuteTempMin = 0;
        heureTempMax = minuteTempMax = 0;
        dateTemp = currentDate;
        wifiLossCount = 0; 
        wifiLossDate = currentDate;
        dateTempMin = "";
        dateTempMax = "";
        Serial.println("Min/Max et compteur Wi-Fi réinitialisés pour " + dateTemp);
    }

    if(t < tempMin){ 
      tempMin = t; 
      heureTempMin = timeinfo->tm_hour; 
      minuteTempMin = timeinfo->tm_min; 
      dateTempMin = getCurrentDateTime(); 
    }
    if(t > tempMax){ 
      tempMax = t; 
      heureTempMax = timeinfo->tm_hour; 
      minuteTempMax = timeinfo->tm_min; 
      dateTempMax = getCurrentDateTime(); 
    }

    if(autoLuminosite) luminositeActuelle = calculLuminositeSimplifie(timeinfo->tm_hour);
    else luminositeActuelle = luminositeFixe;

    display.setBrightness(luminositeActuelle);

    if(currentMode==MODE_TIME){
      int heure = timeinfo->tm_hour;
      int minute = timeinfo->tm_min;
      int valeurAffichee = heure*100 + minute;
      display.showNumberDecEx(valeurAffichee, 0x40, true);
    } else {
      int valeurAffichee=0;
      if(currentMode==MODE_TEMP) valeurAffichee=(int)t;
      else if(currentMode==MODE_HUM) valeurAffichee=(int)h;
      else if(currentMode==MODE_AIR) valeurAffichee=airQuality;
      display.showNumberDec(valeurAffichee,false);
    }
  }

  int numNewMessages = bot.getUpdates(bot.last_message_received + 1);
  while(numNewMessages){
    for(int i=0;i<numNewMessages;i++){
      String text = bot.messages[i].text;
      if(text=="/status"){ sendReport(); }
      else if(text=="/rtech"){ sendTechReport(); }
      else if(text=="/t"){ currentMode=MODE_TEMP; saveConfig(); bot.sendMessage(CHAT_ID,"Affichage température"); }
      else if(text=="/h"){ currentMode=MODE_HUM; saveConfig(); bot.sendMessage(CHAT_ID,"Affichage humidité"); }
      else if(text=="/aq"){ currentMode=MODE_AIR; saveConfig(); bot.sendMessage(CHAT_ID,"Affichage qualité d'air"); }
      else if(text=="/time"){ currentMode=MODE_TIME; saveConfig(); bot.sendMessage(CHAT_ID,"Affichage heure en temps réel (Tunis)"); }
      else if(text=="/l"){ bot.sendMessage(CHAT_ID,"💡 Luminosité actuelle: " + String(luminositeActuelle)); }
      else if(text=="/lmin"){ autoLuminosite=false; luminositeFixe=0x02; luminositeActuelle=luminositeFixe; display.setBrightness(luminositeActuelle); saveConfig(); bot.sendMessage(CHAT_ID,"Luminosité mise au minimum"); }
      else if(text=="/lmax"){ autoLuminosite=false; luminositeFixe=0x07; luminositeActuelle=luminositeFixe; display.setBrightness(luminositeActuelle); saveConfig(); bot.sendMessage(CHAT_ID,"Luminosité mise au maximum"); }
      else if(text=="/lauto"){ autoLuminosite=true; saveConfig(); bot.sendMessage(CHAT_ID,"Luminosité automatique activée"); }
      else if(text=="/c"){
        String cmds = "/t - Affichage température\n";
        cmds += "/h - Affichage humidité\n";
        cmds += "/aq - Affichage qualité d'air\n";
        cmds += "/time - Affichage heure\n";
        cmds += "/l - Niveau de luminosité actuel\n";
        cmds += "/lmin - Luminosité minimum\n";
        cmds += "/lmax - Luminosité maximum\n";
        cmds += "/lauto - Luminosité automatique\n";
        cmds += "/c - Liste des commandes\n";
        cmds += "/config - Voir configuration actuelle\n";
        cmds += "/rtech - Rapport technique\n";
        bot.sendMessage(CHAT_ID,cmds,"Markdown");
      }
      else if(text=="/config"){ sendConfigTelegram(); }
    }
    numNewMessages = bot.getUpdates(bot.last_message_received + 1);
  }
}
